import serial
import modbus_tk
from modbus_tk import modbus_rtu
from modbus_tk.modbus_rtu import RtuMaster
import threading
import paho.mqtt.client as mqtt
import time
import json
import hmac
import base64
import ntptime


class counter_hub():
    def __init__(self, appId, appSecret, upload_protocol, upload_server, start_reg=0, port='/dev/ttyO1'):
        '''
        [summary]
        counter_hub为8个IN口的统一读取模块
        Args:
            name ([str]): [上级网关ID]
            start_reg ([int]): [该IN口的寄存器起始位,默认读八位]
            port (str, optional): [该IN口的485串口名,默认'/dev/tty01']. Defaults to '/dev/ttyO1'.
        '''
        self.start_reg = start_reg
        self.port = port
        self.upload_protocol = upload_protocol
        self.upload_server = upload_server
        self.appId = appId
        self.appSecret = appSecret
        self.nodeName = nodeName

        
        # self.upload_topic = f"{appId}/{devName}/{nodeName}"

        self.upload_flag = False
        self.serial_flag = False
        self.interval = 0.1
        self.last_read = [0]*8
        self.cur_read = [0]*8
        self.count = [0]*8
        self.orign_json = {
            "items": [
                {
                    "ts": 123123123,
                    "value": 100
                }
            ]
        }
        try:
            self.connect_to_upload_server()
        except Exception as e:
            print(e.args[-1])
            print('please check your internet config and reboot')

        try:
            self.connect_to_serial()
        except Exception as e:
            print(e.args[-1])
        '''
        [变量解释]
            connection(RTU Master)：[用于连接后的操作，在未连接时为None]
            upload_action(function):[用户定义的回调函数]
            interval(double):[间隔时间]
            last_read(list):[上次读的数据]
            cur_read(list):[本次读的数据]
            count(list):[所记个数]
        '''

    def connect_to_upload_server(self):
        '''
        [连接到上传服务器，例如mqtt/mqtts]
        self.upload_server格式：
        {
            "host":frankgu.club, #域名或ip
            "port":1883, #端口 mqtt为1883，mqtts为8883
            "heartbeat":60 #心跳包时间
        }
        '''
        def on_connect(client, userdata, flags, rc):
            # print("Connection returned " + str(rc))
            '''
            连接返回码不成功 重连
            '''
            if str(rc) != '0':
                print('Connection return:', str(rc))
                print('----- upload server reconnect -----')
                print('----- wait for 5s -----')
                time.sleep(5)
                self.upload_flag = False

        def on_disconnect(client, userdata, rc):
            '''
            连接断开 重连
            '''
            print('disconnect detected')
            self.upload_flag = False
            self.connect_to_upload_server()

        t = int(time.time())
        self.clientId = f"{self.appId}|sign=hmacsha1,ts={t}|"
        password = hmac.new(self.appSecret.encode('utf-8'), f"{self.appId}/{t}".encode('utf-8'), "sha1")
        password = base64.b64encode(password.digest())
        if self.upload_protocol == 'mqtt':
            self.up_client = mqtt.Client(self.clientId)
            self.up_client.username_pw_set(str(self.appId), password)
            host = self.upload_server['host']
            port = self.upload_server['port']
            heartbeat = self.upload_server['heartbeat']
            self.up_client.on_connect = on_connect
            self.up_client.on_disconnect = on_disconnect
            while self.upload_flag == False:
                try:
                    self.up_client.connect(host, port, heartbeat)
                    # heartbeat_topic = self.gsn + '/0/heartbeat'  # heartbeat topic
                    # self.keep_heartbeat(heartbeat_topic, heartbeat)  # 自动发心跳包
                    print('mqtt is connected')
                    self.upload_flag = True
                except Exception as e:
                    '''
                        连接失败 无返回码 重连
                    '''
                    print(e.args[-1])
                    print('----- upload server reconnect -----')
                    print('----- wait for 5s -----')
                    time.sleep(5)
                    self.upload_flag = False
        elif self.upload_protocol == 'mqtts':
            print('mqtts is not available now')
        else:
            print('this protocol is not available now')

    def keep_heartbeat(self, topic, heartbeat):
        try:
            self.upload_action(topic, '1', 0)
        except Exception as e:
            print(e.args[-1])
            print('Please Check the server config and reboot')
            return
        timer = threading.Timer(
            heartbeat, lambda: self.keep_heartbeat(topic, heartbeat))
        timer.start()

    def connect_to_serial(self):
        '''
        [连接函数]
            设置连接时的端口，波特率9600，读取的位数为8位，无校验位，停止位1
        '''
        while self.serial_flag == False:
            try:
                self.connection = modbus_rtu.RtuMaster(serial.Serial(port=self.port,
                                                                     baudrate=9600,
                                                                     bytesize=8,
                                                                     parity="N",
                                                                     stopbits=1,
                                                                     xonxoff=0))

                self.connection.set_timeout(5)
                self.connection.set_verbose(True)
                print('connect to data collector')
                self.serial_flag = True
            except Exception as err:
                '''
                    连接失败 重连
                '''
                print(err)
                print('----- serial reconnect -----')
                print('----- wait for 5s -----')
                time.sleep(5)
                self.serial_flag = False
                continue

    def upload_action(self, topic, payload, qos):
        if self.upload_protocol == 'mqtt':
            self.up_client.publish(topic, payload, qos)
            print('upload to ', topic, ' success')
        elif self.upload_protocol == 'mqtts':
            print('upload failed')
            print('mqtts is not available now')
        else:
            print('upload failed')
            print('this protocol is not available now')

    def get_data(self):
        '''
        [读值函数]
            获取IN口数值：设备地址为01，功能码为02即读取IN口输入状态，IN口名称，读取的位数
        '''
        data = self.connection.execute(1, 2, self.start_reg, 8)
        data = list(data)
        self.last_read = self.cur_read
        self.cur_read = data
        print(f"data: {data}")
        print(f"last_read: {self.last_read}")

    def start_count(self):
        '''
        [计数函数]
            若上次读值为0且本次读值为1，则计数值加1，并将计数值上传至服务器，每过0.01秒读一次值
        '''
        succes_flag = 1
        try:
            self.get_data()
        except Exception as e:
            '''
                读不到数据 重连
            '''
            succes_flag = 0
            print(e.args[-1])
            print('Please Check the serial and Reboot')
            self.connect_to_serial()
        if succes_flag == 0:
            pass
        else:
            # 分别比较self.last_read和self.cur_read的每一位，任意一位产生变化，就需要上传一次mqtt
            if self.last_read != self.cur_read:
                its = int(time.time()) - 3600*8      # UTC时间转北京时间
                for i in range(8):
                    if self.last_read[i] == 0 and self.cur_read[i] == 1:
                        self.count[i] += 1
                        payload = self.orign_json

                        payload["items"][0]["ts"] = its
                        payload["items"][0]["value"] = self.count[i]
                        
                        tmpstr = json.dumps(payload)
                        try:
                            print(f"topic: {self.upload_action}")
                            print(f"payload: {tmpstr}")
                            self.upload_action(
                                upload_topic[i], tmpstr, 0)
                        except Exception as e:
                            print(e.args[-1])
                            print('Please Check the server config and reboot')

        timer = threading.Timer(self.interval, lambda: self.start_count())
        timer.start()


if __name__ == "__main__":
    print('----- wait for 5s -----')
    time.sleep(5)
    upload_protocol = 'mqtt'
    with open('upload_server.json', 'r') as f:
        upload_server = json.load(f)

    with open('gateway_nodeinfo.json', 'r') as f:
        info = json.load(f)
        appId = info["appId"]
        appSecret = info["appSecret"]
        devName = info["devName"]
        nodeName = info["nodeName"]
        upload_topic = []
        for node in nodeName:
            upload_topic.append(f"{appId}/{devName}/{node}")

    ntptime.GetNtpTime(upload_server["ntp"])        # ntp时间校准

    counthub = counter_hub(
        appId, appSecret, upload_protocol=upload_protocol, upload_server=upload_server)  # 可加port参数，默认为/dev/ttyO1(大写的欧一 不是零一)
    counthub.start_count()
